import LogicFlow, {
  BaseEdgeModel,
  BaseNodeModel,
  EventType,
  CallbackArgs,
  Model,
  transformNodeData,
  transformEdgeData,
} from '@logicflow/core'
import { assign, cloneDeep, filter, forEach, has, map, sortBy } from 'lodash-es'
import { DynamicGroupNode } from './node'
import { DynamicGroupNodeModel } from './model'
import { isAllowMoveTo, isBoundsInGroup } from './utils'

import GraphConfigData = LogicFlow.GraphConfigData
import GraphElements = LogicFlow.GraphElements
import EdgeConfig = LogicFlow.EdgeConfig
import EdgeData = LogicFlow.EdgeData
import NodeData = LogicFlow.NodeData
import BoxBoundsPoint = Model.BoxBoundsPoint
import ElementsInfoInGroup = DynamicGroup.ElementsInfoInGroup

export * from './node'
export * from './model'

export const dynamicGroup = {
  type: 'dynamic-group',
  view: DynamicGroupNode,
  model: DynamicGroupNodeModel,
}

const DEFAULT_TOP_Z_INDEX = -1000
const DEFAULT_BOTTOM_Z_INDEX = -10000

export class DynamicGroup {
  static pluginName = 'dynamicGroup'

  private lf: LogicFlow
  topGroupZIndex: number = DEFAULT_BOTTOM_Z_INDEX
  // 激活态的 group 节点
  activeGroup?: DynamicGroupNodeModel
  // 存储节点与 group 的映射关系
  nodeGroupMap: Map<string, string> = new Map()

  constructor({ lf, options }: LogicFlow.IExtensionProps) {
    lf.register(dynamicGroup)
    this.lf = lf
    assign(this, options)
    // 初始化插件，从监听事件开始及设置规则开始
    this.init()
  }

  /**
   * 获取节点所属的分组
   * @param nodeId
   */
  getGroupByNodeId(nodeId: string) {
    const groupId = this.nodeGroupMap.get(nodeId)
    if (groupId) {
      return this.lf.getNodeModelById(groupId)
    }
  }

  /**
   * 获取自定位置及其所属分组
   * 当分组重合时，优先返回最上层的分组
   * @param bounds
   * @param nodeData
   */
  getGroupByBounds(
    bounds: BoxBoundsPoint,
    nodeData: NodeData,
  ): DynamicGroupNodeModel | undefined {
    const { nodes } = this.lf.graphModel
    const groups = filter(nodes, (node) => {
      return (
        !!node.isGroup &&
        isBoundsInGroup(bounds, node) &&
        node.id !== nodeData.id
      )
    })

    const count = groups.length
    if (count <= 1) {
      return groups[0] as DynamicGroupNodeModel
    } else {
      let topZIndexGroup = groups[count - 1]
      for (let i = count - 2; i >= 0; i--) {
        if (groups[i].zIndex > topZIndexGroup.zIndex) {
          topZIndexGroup = groups[i]
        }
      }
      return topZIndexGroup as DynamicGroupNodeModel
    }
  }

  /**
   * 提高元素的层级，如果是 group，同时提高其子元素的层级
   * @param model
   */
  sendNodeToFront(model?: BaseNodeModel) {
    if (!model || !model.isGroup) return

    this.topGroupZIndex++
    model.setZIndex(this.topGroupZIndex)
    if (model.children) {
      const { children } = model as DynamicGroupNodeModel
      forEach(Array.from(children), (nodeId) => {
        const node = this.lf.getNodeModelById(nodeId)
        this.sendNodeToFront(node)
      })
    }
  }

  /**
   * 递归计算某个分组内最高的 zIndex 值
   * TODO: 这块儿有点疑问❓如果 node 不是 group，这块儿返回的 maxZIndex 是最小值，但 node 的 zIndex 不一定是这个值
   * @param node
   */
  getMaxZIndex(node: BaseNodeModel) {
    let maxZIndex = DEFAULT_BOTTOM_Z_INDEX
    if (node.isGroup) {
      maxZIndex = Math.max(maxZIndex, node.zIndex)
    }
    if (node.children) {
      const { children } = node as DynamicGroupNodeModel
      forEach(Array.from(children), (childId) => {
        const child = this.lf.getNodeModelById(childId)
        if (child?.isGroup) {
          const childMaxZIndex = this.getMaxZIndex(child)
          maxZIndex = Math.max(maxZIndex, childMaxZIndex)
        }
      })
    }
    return maxZIndex
  }

  /**
   * 校准当前 topGroupZIndex 的值
   * @param nodes
   */
  calibrateTopGroupZIndex(nodes: NodeData[]) {
    // 初始化时 or 增加新节点时，找出所有 nodes 的最大 zIndex
    let maxZIndex = DEFAULT_BOTTOM_Z_INDEX
    forEach(nodes, (node) => {
      const nodeModel = this.lf.getNodeModelById(node.id)
      if (nodeModel) {
        const currNodeMaxZIndex = this.getMaxZIndex(nodeModel)
        if (currNodeMaxZIndex > maxZIndex) {
          maxZIndex = currNodeMaxZIndex
        }
      }
    })

    // TODO: 不是很理解这块儿的代码逻辑，需要整理一下
    if (this.topGroupZIndex >= maxZIndex) {
      // 一般是初始化时/增加新节点时发生，因为外部强行设置了一个很大的 zIndex
      // 删除节点不会影响目前最高 zIndex 的赋值
      return
    }

    // 新增 nodes 中如果存在 zIndex 比 this.topGroupZIndex 大
    // 说明 this.topGroupZIndex 已经失去意义，代表不了目前最高 zIndex 的 group，需要重新校准

    // https://github.com/didi/LogicFlow/issues/1535
    // 当外部直接设置多个 BaseNode.zIndex = 1 时
    // 当点击某一个 node 时，由于这个 this.topGroupZIndex 是从 -10000 开始计算的，
    // this.topGroupZIndex + 1 也就是-9999，这就造成当前点击的 node 的 zIndex 远远
    // 比其它 node 的 zIndex 小，因此造成 zIndex 错乱的问题
    // TODO: 这儿的 nodes 能否直接用传参进来的 nodes 呢？？？
    const allNodes = this.lf.graphModel.nodes
    const allGroups = filter(allNodes, (node) => !!node.isGroup)

    let max = this.topGroupZIndex
    forEach(allGroups, (group) => {
      if (group.zIndex > max) max = group.zIndex
    })
    this.topGroupZIndex = max
  }

  onSelectionDrop = () => {
    const { nodes: selectedNodes } = this.lf.graphModel.getSelectElements()
    selectedNodes.forEach((node) => {
      this.addNodeToGroup(node)
    })
  }

  onNodeAddOrDrop = ({ data: node }: CallbackArgs<'node:add'>) => {
    this.addNodeToGroup(node)
  }

  addNodeToGroup = (node: LogicFlow.NodeData) => {
    // 1. 如果该节点之前已经在 group 中了，则将其从之前的 group 移除
    const preGroupId = this.nodeGroupMap.get(node.id)

    if (preGroupId) {
      const group = this.lf.getNodeModelById(
        preGroupId,
      ) as DynamicGroupNodeModel

      group.removeChild(node.id)
      this.nodeGroupMap.delete(node.id)
      group.setAllowAppendChild(false)
    }

    // 2. 然后再判断这个节点是否在某个 group 范围内，如果是，则将其添加到对应的 group 中
    const nodeModel = this.lf.getNodeModelById(node.id)
    const bounds = nodeModel?.getBounds()

    if (nodeModel && bounds) {
      // TODO: 确认下面的注释内容
      // https://github.com/didi/LogicFlow/issues/1261
      // 当使用 SelectionSelect 框选后触发 lf.addNode(Group)
      // 会触发 appendNodeToGroup() 的执行
      // 由于 this.getGroup() 会判断 node.id !== nodeData.id
      // 因此当 addNode 是 Group 类型时，this.getGroup() 会一直返回空
      // 导致了下面这段代码无法执行，也就是无法将当前添加的 Group 添加到 this.nodeGroupMap 中
      // 这导致了折叠分组时触发的 foldEdge() 无法正确通过 getNodeGroup() 拿到正确的 groupId
      // 从而导致折叠分组时一直都会创建一个虚拟边
      // 而初始化分组时由于正确设置了nodeGroupMap的数据，因此不会产生虚拟边的错误情况
      if (nodeModel.isGroup) {
        const group = nodeModel as DynamicGroupNodeModel
        forEach(Array.from(group.children), (childId) => {
          this.nodeGroupMap.set(childId, node.id)
        })
        // 新增 node 时进行 this.topGroupZIndex 的校准更新
        this.calibrateTopGroupZIndex([node])
        this.onNodeSelect({
          data: node,
          isSelected: false,
          isMultiple: false,
        })
      }

      // TODO: 找到这个范围内的 groupModel, 并加 node 添加到该 group
      const group = this.getGroupByBounds(bounds, node)
      if (group) {
        const isAllowAppendIn = group.isAllowAppendIn(node)
        if (isAllowAppendIn) {
          group.addChild(node.id)
          // 建立节点与 group 的映射关系放在了 group.addChild 触发的事件中，与直接调用 addChild 的行为保持一致
          group.setAllowAppendChild(false)
        } else {
          // 抛出不允许插入的事件
          this.lf.emit('group:not-allowed', {
            group: group.getData(),
            node,
          })
        }
      }
    }
  }

  onGroupAddNode = ({
    data: groupData,
    childId,
  }: CallbackArgs<'group:add-node'>) => {
    this.nodeGroupMap.set(childId, groupData.id)
  }

  removeNodeFromGroup = ({
    data: node,
    model,
  }: CallbackArgs<'node:delete'>) => {
    if (model.isGroup && node.children) {
      forEach(
        Array.from((node as DynamicGroupNodeModel).children),
        (childId) => {
          this.nodeGroupMap.delete(childId)
          this.lf.deleteNode(childId)
        },
      )
    }

    const groupId = this.nodeGroupMap.get(node.id)
    if (groupId) {
      const group = this.lf.getNodeModelById(groupId)
      group && (group as DynamicGroupNodeModel).removeChild(node.id)
      this.nodeGroupMap.delete(node.id)
    }
  }

  onSelectionDrag = () => {
    const { nodes: selectedNodes } = this.lf.graphModel.getSelectElements()
    selectedNodes.forEach((node) => {
      this.setActiveGroup(node)
    })
  }
  onNodeDrag = ({ data: node }: CallbackArgs<'node:drag'>) => {
    this.setActiveGroup(node)
  }

  setActiveGroup = (node: LogicFlow.NodeData) => {
    const nodeModel = this.lf.getNodeModelById(node.id)
    const bounds = nodeModel?.getBounds()

    if (nodeModel && bounds) {
      const targetGroup = this.getGroupByBounds(bounds, node)
      if (this.activeGroup) {
        this.activeGroup.setAllowAppendChild(false)
      }

      if (!targetGroup || (nodeModel.isGroup && targetGroup.id === node.id)) {
        return
      }

      const isAllowAppendIn = targetGroup.isAllowAppendIn(node)
      if (!isAllowAppendIn) return

      this.activeGroup = targetGroup
      this.activeGroup.setAllowAppendChild(true)
    }
  }

  /**
   * 1. 分组节点默认在普通节点下面
   * 2. 分组节点被选中后，会将分组节点以及其内部的其它分组节点放到其余分组节点的上面
   * 3. 分组节点取消选中后，不会将分组节点重置为原来的高度
   * 4. 由于 LogicFlow 核心目标是支持用户手动绘制流程图，所以暂时不支持一张流程图超过 1000 个分组节点的情况
   * @param node
   * @param isMultiple
   * @param isSelected
   */
  onNodeSelect = ({
    data: node,
    isMultiple,
    isSelected,
  }: Omit<CallbackArgs<'node:click'>, 'e' | 'position'>) => {
    const nodeModel = this.lf.getNodeModelById(node.id)
    this.sendNodeToFront(nodeModel)

    // 重置所有 group 的 zIndex，防止 group 节点 zIndex 增长为正数（目的是保持 group 节点在最底层）
    if (this.topGroupZIndex > DEFAULT_TOP_Z_INDEX) {
      const { nodes } = this.lf.graphModel
      this.topGroupZIndex = DEFAULT_BOTTOM_Z_INDEX
      const groups = sortBy(
        filter(nodes, (node) => !!node.isGroup),
        'zIndex',
      )

      let preZIndex = 0
      forEach(groups, (group) => {
        if (group.zIndex !== preZIndex) {
          this.topGroupZIndex++
          preZIndex = group.zIndex
        }
        group.setZIndex(this.topGroupZIndex)
      })
    }

    // FIX #1004
    // 如果节点被多选，
    // 这个节点是分组，则将分组的所有子节点取消选中
    // 这个节点是分组的子节点，且其所属分组节点已选，则取消选中
    if (isMultiple && isSelected) {
      if (nodeModel?.isGroup) {
        const { children } = nodeModel as DynamicGroupNodeModel
        forEach(Array.from(children), (childId) => {
          const childModel = this.lf.getNodeModelById(childId)
          childModel?.setSelected(false)
        })
      } else {
        const groupId = this.nodeGroupMap.get(node.id)
        if (groupId) {
          const graphModel = this.lf.getNodeModelById(groupId)
          graphModel?.isSelected && nodeModel?.setSelected(false)
        }
      }
    }
  }

  onNodeMove = ({
    deltaX,
    deltaY,
    data,
  }: Omit<CallbackArgs<'node:mousemove'>, 'e' | 'position'>) => {
    const { id, x, y, properties } = data
    if (!properties) {
      return
    }
    const { width, height } = properties
    const groupId = this.nodeGroupMap.get(id)
    if (!groupId) {
      return
    }
    const groupModel = this.lf.getNodeModelById(
      groupId,
    ) as DynamicGroupNodeModel

    if (!groupModel || !groupModel.isRestrict || !groupModel.autoResize) {
      return
    }
    // 当父节点isRestrict=true & autoResize=true
    // 子节点在父节点中移动时，父节点会自动调整大小

    // step1: 计算出当前child的bounds
    const newX = x + deltaX / 2
    const newY = y + deltaY / 2
    const minX = newX - width! / 2
    const minY = newY - height! / 2
    const maxX = newX + width! / 2
    const maxY = newY + height! / 2
    // step2：比较当前child.bounds与parent.bounds的差异，比如child.minX<parent.minX，那么parent.minX=child.minX
    let hasChange = false
    const groupBounds = groupModel.getBounds()
    const newGroupBounds = Object.assign({}, groupBounds)
    if (minX < newGroupBounds.minX) {
      newGroupBounds.minX = minX
      hasChange = true
    }
    if (minY < newGroupBounds.minY) {
      newGroupBounds.minY = minY
      hasChange = true
    }
    if (maxX > newGroupBounds.maxX) {
      newGroupBounds.maxX = maxX
      hasChange = true
    }
    if (maxY > newGroupBounds.maxY) {
      newGroupBounds.maxY = maxY
      hasChange = true
    }
    if (!hasChange) {
      return
    }
    // step3: 根据当前parent.bounds去计算出最新的x、y、width、height
    const newGroupX =
      newGroupBounds.minX + (newGroupBounds.maxX - newGroupBounds.minX) / 2
    const newGroupY =
      newGroupBounds.minY + (newGroupBounds.maxY - newGroupBounds.minY) / 2
    const newGroupWidth = newGroupBounds.maxX - newGroupBounds.minX
    const newGroupHeight = newGroupBounds.maxY - newGroupBounds.minY
    groupModel.moveTo(newGroupX, newGroupY)
    groupModel.width = newGroupWidth
    groupModel.height = newGroupHeight
  }

  onGraphRendered = ({ data }: CallbackArgs<'graph:rendered'>) => {
    forEach(data.nodes, (node) => {
      if (node.children) {
        forEach(node.children, (childId) => {
          this.nodeGroupMap.set(childId, node.id)
        })
      }
    })

    // TODO: 确认一下下面方法的必要性及合理性
    // 初始化 nodes 时进行 this.topGroupZIndex 的校准更新
    this.calibrateTopGroupZIndex(data.nodes)
  }

  removeChildrenInGroupNodeData<
    T extends LogicFlow.NodeData | LogicFlow.NodeConfig,
  >(nodeData: T) {
    const newNodeData = cloneDeep(nodeData)
    delete newNodeData.children
    if (newNodeData.properties?.children) {
      delete newNodeData.properties.children
    }
    return newNodeData
  }

  /**
   * 创建一个 Group 类型节点内部所有子节点的副本
   * 并且在遍历所有 nodes 的过程中，顺便拿到所有 edges (只在 Group 范围的 edges)
   */
  initGroupChildNodes(
    nodeIdMap: Record<string, string>,
    children: Set<string>,
    curGroup: DynamicGroupNodeModel,
    distance: number,
  ): ElementsInfoInGroup {
    // Group 中所有子节点
    const allChildNodes: BaseNodeModel[] = []
    // 属于 Group 内部边的 EdgeData
    const edgesDataArr: EdgeData[] = []
    // 所有有关联的连线
    const allRelatedEdges: BaseEdgeModel[] = []

    forEach(Array.from(children), (childId: string) => {
      const childNode = this.lf.getNodeModelById(childId)
      if (childNode) {
        const childNodeChildren = childNode.children
        const childNodeData = childNode.getData()
        const eventType = EventType.NODE_GROUP_COPY || 'node:group-copy-add'

        const newNodeConfig = transformNodeData(
          this.removeChildrenInGroupNodeData(childNodeData),
          distance,
        )
        const tempChildNode = this.lf.addNode(newNodeConfig, eventType)
        curGroup.addChild(tempChildNode.id)

        nodeIdMap[childId] = tempChildNode.id // id 同 childId，做映射存储
        allChildNodes.push(tempChildNode)

        // 1. 存储 children 内部节点相关的输入边（incoming）
        allRelatedEdges.push(
          ...[...tempChildNode.incoming.edges, ...tempChildNode.outgoing.edges],
        )

        if (childNodeChildren instanceof Set) {
          const { childNodes, edgesData } = this.initGroupChildNodes(
            nodeIdMap,
            childNodeChildren,
            tempChildNode as DynamicGroupNodeModel,
            distance,
          )

          allChildNodes.push(...childNodes)
          edgesDataArr.push(...edgesData)
        }
      }
    })

    // 1. 判断每一条边的开始节点、目标节点是否在 Group 中
    const edgesInnerGroup = filter(allRelatedEdges, (edge) => {
      return (
        has(nodeIdMap, edge.sourceNodeId) && has(nodeIdMap, edge.targetNodeId)
      )
    })
    // 2. 为「每一条 Group 的内部边」构建出 EdgeData 数据，得到 EdgeConfig，生成新的线
    const edgesDataInnerGroup = map(edgesInnerGroup, (edge) => {
      return edge.getData()
    })

    return {
      childNodes: allChildNodes,
      edgesData: edgesDataArr.concat(edgesDataInnerGroup),
    }
  }

  /**
   * 根据参数 edge 选择是新建边还是基于已有边，复制一条边出来
   * @param edge
   * @param nodeIdMap
   * @param distance
   */
  createEdge(
    edge: EdgeConfig | EdgeData,
    nodeIdMap: Record<string, string>,
    distance: number,
  ) {
    const { sourceNodeId, targetNodeId } = edge
    const sourceId = nodeIdMap[sourceNodeId] ?? sourceNodeId
    const targetId = nodeIdMap[targetNodeId] ?? targetNodeId

    // 如果是有 id 且 text 是对象的边，需要重新计算位置，否则直接用 edgeConfig 生成边
    let newEdgeConfig = cloneDeep(edge)
    if (edge.id && typeof edge.text === 'object' && edge.text !== null) {
      newEdgeConfig = transformEdgeData(edge as EdgeData, distance)
    }

    return this.lf.graphModel.addEdge({
      ...newEdgeConfig,
      sourceNodeId: sourceId,
      targetNodeId: targetId,
    })
  }

  /**
   * 检测group:resize后的bounds是否会小于children的bounds
   * 限制group进行resize时不能小于内部的占地面积
   * @param groupModel
   * @param deltaX
   * @param deltaY
   * @param newWidth
   * @param newHeight
   */
  checkGroupBoundsWithChildren(
    groupModel: DynamicGroupNodeModel,
    deltaX: number,
    deltaY: number,
    newWidth: number,
    newHeight: number,
  ) {
    if (groupModel.children) {
      const { children, x, y } = groupModel
      // 根据deltaX和deltaY计算出当前model的bounds
      const newX = x + deltaX / 2
      const newY = y + deltaY / 2
      const groupMinX = newX - newWidth / 2
      const groupMinY = newY - newHeight / 2
      const groupMaxX = newX + newWidth / 2
      const groupMaxY = newY + newHeight / 2

      const childrenArray = Array.from(children)
      for (let i = 0; i < childrenArray.length; i++) {
        const childId = childrenArray[i]
        const child = this.lf.getNodeModelById(childId)
        if (!child) {
          continue
        }
        const childBounds = child.getBounds()
        const { minX, minY, maxX, maxY } = childBounds
        // parent:resize后的bounds不能小于child:bounds，否则阻止其resize
        const canResize =
          groupMinX <= minX &&
          groupMinY <= minY &&
          groupMaxX >= maxX &&
          groupMaxY >= maxY
        if (!canResize) {
          return false
        }
      }
    }

    return true
  }

  /**
   * Group 插件的初始化方法
   * TODO：1. 待讨论，可能之前插件分类是有意义的 components, material, tools
   * 区别是：1. 有些插件就是自定义节点，可能会有初始化方法 init，但不必要有 render （比如 Group）
   * 2. 有些插件是渲染一些部件（比如 MiniMap、Control、Menu 等）必须要有 render
   * 3. 工具类的，init 、 render
   * 该如何分类呢？并如何完善插件的类型
   *
   * TODO: 2. 插件的 destroy 方法该做些什么，是否应该加 destroy 方法
   * TODO: 3. 是否应该定义一个 Extension 的基类，所有插件基于这个基类来开发，这样在初始化的时候就可以确认执行什么方法
   */
  init() {
    const { lf } = this
    const { graphModel } = lf
    // 添加分组节点移动规则
    // 1. 移动分组节点时，同时移动分组内所有节点
    // 2. 移动子节点时，判断是否有限制规则（isRestrict）
    graphModel.addNodeMoveRules((model, deltaX, deltaY) => {
      // 判断如果是 group，移动时需要同时移动组内的所有节点
      if (model.isGroup) {
        // https://github.com/didi/LogicFlow/issues/1826
        // 这里不应该触发移动子节点的逻辑，这里是判断是否可以移动，而不是触发移动逻辑
        // 而且这里触发移动，会导致resize操作的this.x变动也会触发子item的this.x变动
        // resize时的deltaX跟正常移动的deltaX是不同的

        // const nodeIds = this.getNodesInGroup(model as DynamicGroupNodeModel)
        // graphModel.moveNodes(nodeIds, deltaX, deltaY, true)
        return true
      }

      const groupId = this.nodeGroupMap.get(model.id)!
      const groupModel = this.lf.getNodeModelById(
        groupId,
      ) as DynamicGroupNodeModel

      if (groupModel && groupModel.isRestrict) {
        if (groupModel.autoResize) {
          // 子节点在父节点中移动时，父节点会自动调整大小
          // 在node:mousemove中进行父节点的调整
          return true
        } else {
          // 如果移动的节点存在于某个分组中，且这个分组禁止子节点移出去
          const groupBounds = groupModel.getBounds()
          return isAllowMoveTo(groupBounds, model, deltaX, deltaY)
        }
      }

      return true
    })

    // https://github.com/didi/LogicFlow/issues/1442
    // https://github.com/didi/LogicFlow/issues/937
    // 添加分组节点resize规则
    // isRestrict限制模式下，当前model resize时不能小于children的占地面积
    // 并且在isRestrict限制模式下，transformWidthContainer即使设置为true，也无效
    graphModel.addNodeResizeRules((model, deltaX, deltaY, width, height) => {
      if (model.isGroup && model.isRestrict) {
        return this.checkGroupBoundsWithChildren(
          model as DynamicGroupNodeModel,
          deltaX,
          deltaY,
          width,
          height,
        )
      }
      return true
    })

    graphModel.dynamicGroup = this
    lf.on('node:add,node:drop,node:dnd-add', this.onNodeAddOrDrop)
    lf.on('selection:drop', this.onSelectionDrop)
    lf.on('node:delete', this.removeNodeFromGroup)
    lf.on('node:drag,node:dnd-drag', this.onNodeDrag)
    lf.on('selection:drag', this.onSelectionDrag)
    lf.on('node:click', this.onNodeSelect)
    lf.on('node:mousemove', this.onNodeMove)
    lf.on('graph:rendered', this.onGraphRendered)

    lf.on('group:add-node', this.onGroupAddNode)

    // https://github.com/didi/LogicFlow/issues/1346
    // 重写 addElements() 方法，在 addElements() 原有基础上增加对 group 内部所有 nodes 和 edges 的复制功能
    // 使用场景：addElements api 项目内部目前只在快捷键粘贴时使用（此处解决的也应该是粘贴场景的问题）
    lf.addElements = (
      { nodes: selectedNodes, edges: selectedEdges }: GraphConfigData,
      distance = 40,
    ): GraphElements => {
      // oldNodeId -> newNodeId 映射 Map
      const nodeIdMap: Record<string, string> = {}
      // 本次添加的所有节点和边
      const elements: GraphElements = {
        nodes: [],
        edges: [],
      }
      // 所有属于分组内的边 -> sourceNodeId 和 targetNodeId 都在 Group 内
      const edgesInnerGroup: EdgeData[] = []

      forEach(selectedNodes, (node) => {
        const originId = node.id
        const children = node.properties?.children ?? node.children

        const model = lf.addNode(this.removeChildrenInGroupNodeData(node))

        if (originId) nodeIdMap[originId] = model.id
        elements.nodes.push(model) // 此时为 group 的 nodeModel

        // TODO: 递归创建 group 的 nodeModel 的 children
        if (model.isGroup) {
          const { edgesData } = this.initGroupChildNodes(
            nodeIdMap,
            children,
            model as DynamicGroupNodeModel,
            distance,
          )
          edgesInnerGroup.push(...edgesData)
        }
      })

      forEach(edgesInnerGroup, (edge) => {
        this.createEdge(edge, nodeIdMap, distance)
      })
      forEach(selectedEdges, (edge) => {
        elements.edges.push(this.createEdge(edge, nodeIdMap, distance))
      })

      // 返回 elements 进行选中效果，即触发 element.selectElementById()
      // shortcut.ts 也会对最外层的 nodes 和 edges 进行偏移，即 translationNodeData()
      return elements
    }

    this.render()
  }

  render() {}

  destroy() {
    // 销毁监听的事件，并移除渲染的 dom 内容
    this.lf.off('node:add,node:drop,node:dnd-add', this.onNodeAddOrDrop)
    this.lf.off('selection:drop', this.onSelectionDrop)
    this.lf.off('node:delete', this.removeNodeFromGroup)
    this.lf.off('node:drag,node:dnd-drag', this.onNodeDrag)
    this.lf.off('selection:drag', this.onSelectionDrag)
    this.lf.off('node:click', this.onNodeSelect)
    this.lf.off('node:mousemove', this.onNodeMove)
    this.lf.off('graph:rendered', this.onGraphRendered)
    this.lf.off('group:add-node', this.onGroupAddNode)

    // 还原 lf.addElements 方法？
    // 移除 graphModel 上重写的 addNodeMoveRules 方法？
    // TODO: 讨论一下插件该具体做些什么
  }
}

export namespace DynamicGroup {
  export type ElementsInfoInGroup = {
    childNodes: BaseNodeModel[] // 分组节点的所有子节点 model
    edgesData: EdgeData[] // 属于分组内的线的 EdgeData (即开始节点和结束节点都在 Group 内)
  }

  export type DynamicGroupOptions = Partial<{
    isCollapsed: boolean
  }>
}

export default DynamicGroup
